/****************************************************************************************************************************
 * This is a simple demo to show how the library works.
 * The goal here is not to present a full application, many of its configurations are done directly in code, but to present
 * basic usage of the library. 
 * When the game starts, the search is started too. The application checks for user input in Update and for the search status
 * in the Draw method. When the search finishes and it's succesful, the path is drawn.
 * 
 * Use the following commands to play with the demo (again I'd like to notice that many configurations should be changed 
 * directly in code):
 * - Mouse left click: changes if a node is navigable or not.
 * - R: resets and pauses the search.
 * - E: resets and pauses the seach, and also randomises the map.
 * - S: starts the search if it's not started yet.
 * - Esc: exit.
 ****************************************************************************************************************************/
            
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

// Required namespace.
// For your projects don't forget to add a reference to XnaPathfidingLibrary.dll.
using XnaPathfidingLibrary;

namespace Demo1
{
    /// <summary>
    /// This is a game class that uses the pathfiding lib.
    /// </summary>
    public class Demo1 : Microsoft.Xna.Framework.Game
    {
        /// <summary>
        /// Graphics device manager.
        /// </summary>
        private GraphicsDeviceManager graphics;
        
        /// <summary>
        /// Spritebatch.
        /// </summary>
        private SpriteBatch spriteBatch;

        /// <summary>
        /// Map used in the search. This defines the search space.
        /// Somethings to note:
        /// - This is a NxM matrix. There are some different ways to initialize the map's cells. See constructor for details.
        /// - This is used only for initial configuration. After the map is added to the search method, changes in this variable
        /// aren't reflected anymore. If you need to change anything after this, use searchMethod.Map.Nodes.
        /// </summary>
        private Map map;

        /// <summary>
        /// The search algorithm is derived from AbstractSearch. 
        /// </summary>
        private AbstractSearch searchComponent;

        /// <summary>
        /// Texture for navigable nodes.
        /// </summary>
        private Texture2D navigableTexture;

        /// <summary>
        /// Texture for non navigable nodes.
        /// </summary>
        private Texture2D nonNavigableTexture;

        /// <summary>
        /// Texture for the current selected node.
        /// </summary>
        private Texture2D selectionTexture;

        /// <summary>
        /// Texture for the start node.
        /// </summary>
        private Texture2D startTexture;

        /// <summary>
        /// Texture for the start end.
        /// </summary>
        private Texture2D endTexture;

        /// <summary>
        /// Texture for the current visited node.
        /// </summary>
        private Texture2D characterTexture;

        /// <summary>
        /// Mouse position.
        /// </summary>
        private Point mousePosition;

        /// <summary>
        /// Previous mouse state.
        /// </summary>
        private MouseState previousMouseState;

        /// <summary>
        /// Current mouse state.
        /// </summary>
        private MouseState currentMouseState;

        /// <summary>
        /// Previous keyboard state.
        /// </summary>
        private KeyboardState previousKeyboardState;

        /// <summary>
        /// Current keyboard state.
        /// </summary>
        private KeyboardState currentKeyboardState;

        /// <summary>
        /// Constructor.
        /// </summary>
        public Demo1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        /// <summary>
        /// Initialize.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here

            // Creates a map with 30 lines and 40 columns.
            // This constructor creates a fully navigable map. We will randomise it's cells later, in order to get some obstacles.
            // If you want to use your own map format, you should create a method that translates your format to this format.
            // It's not really hard. Let's say you want the first line, second column node to be non navigable. Do it:
            // map.Nodes[0, 1].Navigable = false;
            // If you want to use this map format but need more info within each node, there's a Data field that can store any object.
            // It works like this:
            // map.Nodes[0, 0].Data = new GameComponent(this);
            // The above line stores a GameComponent in node (0,0). Any other types are possible too, so create a class that stores
            // all the info you need to a node, and add it to the Data field.
            map = new Map(30, 40);            
            // The top left cell is the start node. 
            // Change it if you want another node. Remember the first value is line and the second is column.
            map.StartNode = map.Nodes[0, 0];
            // The botton right cell is the end node.
            // Change it if you want another node. Remember the first value is line and the second is column.
            map.EndNode = map.Nodes[29, 39];

            // Makes the other cells random.
            RandomiseMap();

            // Creates an object to manage the search.
            // Here we use an A* search algorithm, but you should try others too.
            searchComponent = new AStarSearch(this);
            // Provides a search map.
            searchComponent.Map = map;
            // Time limit in milliseconds. This says everytime the search's Update method is called, it will have 0.01 milliseconds
            // of processing time until it needs to return and let the game go on. You can adjust this time to give more or less
            // time for the search, each frame. Remember that at least one node you be visited per frame, even if it takes more than
            // the time limit.
            searchComponent.TimeLimit = 0.01;
            // Amount of iterations (visited nodes) the search algorithm can take each frame. 
            // It works only if Configuration is SearchConfiguration.IterationLimited.
            searchComponent.IterationLimit = 1;
            // Defines that the search will be timed, as explained before. You can change it for NotTimed or IterationLimited.
            searchComponent.Configuration = SearchConfiguration.Timed;
            // Defines the neighbors are visited counterclockwise.
            searchComponent.Orientation = SearchOrientation.Counterclockwise;
            // Adds the search component.
            Components.Add(searchComponent);

            // Starts the search.
            // When the search ends, a OnSearchFinished event is triggered, if you provided one.
            // Using this event is one way to check if the search is finished and it's results.
            // Another way is to check the searchComponent's state, like we do in the Draw method, below.
            searchComponent.Start();

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            // Loads textures.
            navigableTexture = Content.Load<Texture2D>("grass");
            nonNavigableTexture = Content.Load<Texture2D>("wall");
            selectionTexture = Content.Load<Texture2D>("selection");
            startTexture = Content.Load<Texture2D>("start");
            endTexture = Content.Load<Texture2D>("end");
            characterTexture = Content.Load<Texture2D>("character");
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Updates mouse and keyboard states.
            previousMouseState = currentMouseState;
            currentMouseState = Mouse.GetState();
            previousKeyboardState = currentKeyboardState;
            currentKeyboardState = Keyboard.GetState();

            // Gets current mouse position.
            mousePosition = new Point(currentMouseState.X / navigableTexture.Width, currentMouseState.Y / navigableTexture.Height);

            // Exit.
            if (currentKeyboardState.IsKeyDown(Keys.Escape))
            {
                Exit();
            }

            // Left button clicks toggles the node's navigability.
            if (currentMouseState.LeftButton == ButtonState.Pressed && previousMouseState.LeftButton == ButtonState.Released)
            {
                if (mousePosition.Y < searchComponent.Map.Nodes.GetLength(0) && mousePosition.Y >= 0
                    && mousePosition.X < searchComponent.Map.Nodes.GetLength(1) && mousePosition.X >= 0)
                {
                    searchComponent.Map.Nodes[mousePosition.Y, mousePosition.X].Navigable 
                        = !searchComponent.Map.Nodes[mousePosition.Y, mousePosition.X].Navigable;
                }
            }

            // Press R to reset the search. It pauses after that.
            if (currentKeyboardState.IsKeyDown(Keys.R) && previousKeyboardState.IsKeyUp(Keys.R))
            {
                searchComponent.Reset();
            }

            // Press E to reset the search and randomise the map. It pauses after that.
            if (currentKeyboardState.IsKeyDown(Keys.E) && previousKeyboardState.IsKeyUp(Keys.E))
            {
                RandomiseMap();
                searchComponent.Reset();
            }

            // Press S to start the search. Works only if it's not started yet.
            if (currentKeyboardState.IsKeyDown(Keys.S) && previousKeyboardState.IsKeyUp(Keys.S))
            {
                if (searchComponent.State == SearchState.NotStarted)
                {
                    searchComponent.Start();
                }
            }

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            spriteBatch.Begin();

            // Draw the map.
            // Important: this is not a map library, the map here is used only as search space.
            // This is why no default draw method is provided to the map. If you use you own map format, draw it as you will.
            // This method just draw different textures for each node if it's navigable or not. Visited nodes are tinted gray.
            for (int i = 0; i < map.Nodes.GetLength(0); i++)
            {
                for (int j = 0; j < map.Nodes.GetLength(1); j++)
                {
                    MapNode n = map.Nodes[i, j];

                    if (n.Navigable)
                    {
                        if (!n.Visited)
                        {
                            spriteBatch.Draw(navigableTexture,
                                new Vector2(j * navigableTexture.Width, i * navigableTexture.Height), Color.White);
                        }
                        else
                        {
                            spriteBatch.Draw(navigableTexture,
                                new Vector2(j * navigableTexture.Width, i * navigableTexture.Height), Color.Gray);
                        }
                    }
                    else
                    {
                        spriteBatch.Draw(nonNavigableTexture,
                               new Vector2(j * nonNavigableTexture.Width, i * nonNavigableTexture.Height), Color.White);
                    }
                }
            }

            // If search is finished and the goal was found, draw the path.
            if (searchComponent.State == SearchState.FinishedFound)
            {
                LinkedList<MapNode> path = searchComponent.Path;
                LinkedListNode<MapNode> node = path.First;

                while (node != null)
                {
                    MapNode n = node.Value;
                    node = node.Next;
                    spriteBatch.Draw(navigableTexture,
                        new Vector2(n.Position.X * navigableTexture.Width, n.Position.Y * navigableTexture.Height), Color.Red);
                }
            }

            // Draw the starting point.
            spriteBatch.Draw(startTexture,
                new Vector2(map.StartNode.Position.X * startTexture.Width, map.StartNode.Position.Y * startTexture.Height), Color.White);

            // Draw the ending point.
            spriteBatch.Draw(endTexture,
                new Vector2(map.EndNode.Position.X * endTexture.Width, map.EndNode.Position.Y * endTexture.Height), Color.White);

            // Draw the current node being visited.
            if (searchComponent.CurrentNode != null)
            {
                spriteBatch.Draw(characterTexture,
                    new Vector2(searchComponent.CurrentNode.Position.X * characterTexture.Width,
                        searchComponent.CurrentNode.Position.Y * characterTexture.Height), Color.White);
            }

            // Draw the mouse selection texture.
            spriteBatch.Draw(selectionTexture, new Vector2(mousePosition.X * navigableTexture.Width, mousePosition.Y * navigableTexture.Height), Color.White);

            spriteBatch.End();

            base.Draw(gameTime);
        }

        /// <summary>
        /// Randomise the map for easier testing.
        /// </summary> 
        private void RandomiseMap()
        {
            Random r = new Random();

            for (int i = 0; i < map.Nodes.GetLength(0); i++)
            {
                for (int j = 0; j < map.Nodes.GetLength(1); j++)
                {
                    // 20% non navigable
                    if (r.Next(5) == 0)
                    {
                        if (map.StartNode.Position != map.Nodes[i, j].Position
                            && map.EndNode.Position != map.Nodes[i, j].Position)
                        {
                            map.Nodes[i, j].Navigable = false;
                        }
                    }
                    else
                    {
                        map.Nodes[i, j].Navigable = true;
                    }
                }
            }
        }
    }
}
